﻿// 参考 https://learn.microsoft.com/zh-cn/windows/uwp/devices-sensors/gatt-client

using System;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Devices.Bluetooth.Advertisement;
using System.Diagnostics;
using zy_simu.Data;
using Windows.Storage.Streams;
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using System.Threading;
using static zy_simu.Views.BleConfigDialog;
using System.Collections.Generic;
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Linq;
using System.Text;
using System.Windows.Media;

namespace zy_simu.Data
{
    public class EmuCommBle : IEmuComm
    {
        GattCharacteristic? GattCharRead = null;
        GattCharacteristic? GattCharWrite = null;
        [AllowNull] private Task read_task;
        [AllowNull] private Task task_send;
        private CancellationTokenSource read_task_cancel = new CancellationTokenSource();
        private CancellationTokenSource task_send_cancel = new CancellationTokenSource();
        private byte[] read_buf = new byte[4096];
        private int read_buf_pos = 0;
        private byte[] send_buf = new byte[4096];
        private int send_buf_pos = 0;


        private UInt64 device_ble_mac = 0;
        private BluetoothLEDevice? BleDev;
        private readonly int E_DEVICE_NOT_AVAILABLE = unchecked((int)0x800710df); // HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_AVAILABLE)
        private bool subscribed_notification = false;   // 是否注册通知成功
        public EmuCommBle(string device_ble_mac)
        {
            this.device_ble_mac = Convert.ToUInt64(device_ble_mac, 16);
        }
        private void data_recv(object sender, GattValueChangedEventArgs args)
        {
            byte[] buf = Utilities.ReadBufferToBytes(args.CharacteristicValue);
            if (buf.Length <= 0) return;
            lock (read_buf)
            {
                if (buf.Length <= read_buf.Length - read_buf_pos)
                {
                    Array.Copy(buf, 0, read_buf, read_buf_pos, buf.Length);
                    read_buf_pos += buf.Length;
                }
            }
        }
        private bool IsSigDefinedUuid(Guid uuid)
        {
            var bluetoothBaseUuid = new Guid("00000000-0000-1000-8000-00805F9B34FB");

            var bytes = uuid.ToByteArray();
            // Zero out the first and second bytes
            // Note how each byte gets flipped in a section - 1234 becomes 34 12
            // Example Guid: 35918bc9-1234-40ea-9779-889d79b753f0
            //                   ^^^^
            // bytes output = C9 8B 91 35 34 12 EA 40 97 79 88 9D 79 B7 53 F0
            //                ^^ ^^
            bytes[0] = 0;
            bytes[1] = 0;
            var baseUuid = new Guid(bytes);
            return baseUuid == bluetoothBaseUuid;
        }
        private string GetServiceName(GattDeviceService service)
        {
            if (IsSigDefinedUuid(service.Uuid))
            {
                GattNativeServiceUuid serviceName;
                if (Enum.TryParse(Utilities.ConvertUuidToShortId(service.Uuid).ToString(), out serviceName))
                {
                    return serviceName.ToString();
                }
            }
            return "Custom Service: " + service.Uuid;
        }
        private string GetCharacteristicName(GattCharacteristic characteristic)
        {
            if (IsSigDefinedUuid(characteristic.Uuid))
            {
                GattNativeCharacteristicUuid characteristicName;
                if (Enum.TryParse(Utilities.ConvertUuidToShortId(characteristic.Uuid).ToString(), out characteristicName))
                {
                    return characteristicName.ToString();
                }
            }

            if (!string.IsNullOrEmpty(characteristic.UserDescription))
            {
                return characteristic.UserDescription;
            }

            else
            {
                return "Custom Characteristic: " + characteristic.Uuid;
            }
        }

        // 实现IEmuComm -----------------------------------------------------------
        public async Task<bool> Open()
        {
            GattCharRead = GattCharWrite = null;
            try
            {
                // BT_Code: BluetoothLEDevice.FromIdAsync must be called from a UI thread because it may prompt for consent.
                BleDev = await BluetoothLEDevice.FromBluetoothAddressAsync(device_ble_mac);
                if (BleDev == null)
                    return false;

                await Task.Delay(200);

                // If the services supported by the device are expected to change during BT usage, subscribe to the GattServicesChanged event.
                GattDeviceServicesResult result = await BleDev.GetGattServicesAsync(BluetoothCacheMode.Uncached);
                if (result.Status != GattCommunicationStatus.Success)
                    return false;
                GattDeviceService? gatt_dev_service = null;
                foreach (var service in result.Services)
                {
                    if (GetServiceName(service) == "65520") gatt_dev_service = service;
                }
                if (gatt_dev_service == null)
                    return false;

                // Ensure we have access to the device.
                var accessStatus = await gatt_dev_service.RequestAccessAsync();
                if (accessStatus != DeviceAccessStatus.Allowed)
                    return false;

                // BT_Code: Get all the child characteristics of a service. Use the cache mode to specify uncached characterstics only
                // and the new Async functions to get the characteristics of unpaired devices as well.
                var result2 = await gatt_dev_service.GetCharacteristicsAsync(BluetoothCacheMode.Uncached);
                if (result2.Status != GattCommunicationStatus.Success)
                    return false;
                foreach (var characteristic in result2.Characteristics)
                {
                    System.Console.WriteLine(GetCharacteristicName(characteristic));
                    if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Write)
                    || characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.WriteWithoutResponse))
                    {
                        GattCharWrite = characteristic;
                    }
                    // else if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Indicate)
                    // || characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
                    else if (characteristic.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Notify))
                    {
                        GattCharRead = characteristic;
                        var cccdValue = GattClientCharacteristicConfigurationDescriptorValue.Notify;
                        // BT_Code: Must write the CCCD in order for server to send indications.
                        // We receive them in the ValueChanged event handler.
                        GattCommunicationStatus status = await GattCharRead.WriteClientCharacteristicConfigurationDescriptorAsync(cccdValue);
                        if(status == GattCommunicationStatus.Success)
                        {
                            subscribed_notification = true;
                            GattCharRead.ValueChanged += data_recv;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                GattCharRead = GattCharWrite = null;
                System.Console.WriteLine("Ble error: " + ex.ToString());
                return false;
            }
            // start send task
            task_send = new Task(async () =>
            {
                while (!task_send_cancel.IsCancellationRequested)
                {
                    //System.Threading.Thread.Sleep(50);
                    if (send_buf_pos <= 0 || GattCharWrite == null)
                        continue;

                    byte[]? cur_sending = null;
                    lock (send_buf)
                    {
                        cur_sending = new byte[send_buf_pos];
                        Array.Copy(send_buf, cur_sending, send_buf_pos);
                        send_buf_pos = 0;
                    }
                    for (int i = 0, len = 0; i < cur_sending.Length; i += len)
                    {
                        len = cur_sending.Length - i;
                        if (len > 240) len = 240;
                        IBuffer buf = send_buf.AsBuffer(i, len);
                        var result = await GattCharWrite.WriteValueWithResultAsync(buf);
                        if (result.Status == GattCommunicationStatus.Success)
                        {
                            // Successfully wrote to device
                        }
                    }
                }
            });

            System.Threading.Thread.Sleep(100);
            task_send.Start();

            return true;
        }
        public async Task<bool> Close()
        {
            task_send_cancel.Cancel();

            if (subscribed_notification)
            {
                if (GattCharRead != null)
                {
                    var result = await GattCharRead.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None);
                    GattCharRead.ValueChanged -= data_recv;
                }
                subscribed_notification = false;
            }

            //if (GattCharWrite != null)
            //    GattCharWrite.Service.Dispose();
            //if (GattCharRead != null)
            //    GattCharRead.Service.Dispose();
            //if (BleDev != null)
            GattCharRead?.Service.Dispose();
            BleDev?.Dispose();
            BleDev = null;
            GattCharRead = GattCharWrite = null;
            return true;
        }

        public async Task Send(byte[] buf, int cnt)
        {
            if (GattCharWrite == null)
                return;
            lock (send_buf)
            {
                if ((send_buf_pos + cnt) < send_buf.Length)
                {
                    Array.Copy(buf, 0, send_buf, send_buf_pos, cnt);
                    send_buf_pos += cnt;
                }
            }
        }
        public int Recv(byte[] buf, int offset, int cnt)
        {
            lock (read_buf)
            {
                cnt = (cnt > read_buf_pos) ? read_buf_pos : cnt;
                Array.Copy(read_buf, 0, buf, offset, cnt);
                read_buf_pos -= cnt;
                return cnt;
            }
        }
    }
}
